package com.gudy.engine.bean.orderbook;

import com.google.common.collect.Lists;
import com.gudy.engine.bean.command.CmdResultCode;
import com.gudy.engine.bean.command.RbCmd;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.eclipse.collections.impl.map.mutable.primitive.LongObjectHashMap;
import thirdpart.hq.L1MarketData;
import thirdpart.order.OrderDirection;
import thirdpart.order.OrderStatus;

import java.util.*;

@Log4j2
@RequiredArgsConstructor
public class GOrderBookImpl implements IOrderBook {

    @NonNull
    private int code;

    private final NavigableMap<Long,IOrderBucket> sellBuckets = new TreeMap<>();
    private final NavigableMap<Long,IOrderBucket> buyBuckets = new TreeMap<>(Collections.reverseOrder());

    private final LongObjectHashMap<Order> oidMap = new LongObjectHashMap<>();

    @Override
    public CmdResultCode newOrder(RbCmd cmd) {
        //1 判断订单是否重复
        if(oidMap.contains(cmd.oid)){
            return CmdResultCode.DUPLICATE_ORDER_ID;
        }
        //2 生成新的Order
        //预撮合
        NavigableMap<Long,IOrderBucket> subMatchBuckets =
                (cmd.direction == OrderDirection.SELL ? buyBuckets : sellBuckets).headMap(cmd.price,true)
                .headMap(cmd.price,true);

        long tVolumn = preMatch(cmd,subMatchBuckets);
        if(tVolumn == cmd.volume){
            return CmdResultCode.SUCCESS;
        }
        final Order order = Order.builder()
                .mid(cmd.mid)
                .uid(cmd.uid)
                .code(cmd.code)
                .direction(cmd.direction)
                .price(cmd.price)
                .volume(cmd.volume)
                .tvolume(tVolumn)
                .oid(cmd.oid)
                .timestamp(cmd.timestamp)
                .build();

        if(tVolumn == 0){
            genMatchEvent(cmd,OrderStatus.ORDER_ED);
        }else{
            genMatchEvent(cmd,OrderStatus.PART_TRADE);
        }
        //3 加入到orderBucket
        NavigableMap<Long, IOrderBucket> priceMapBucket = cmd.direction == OrderDirection.SELL ? sellBuckets : buyBuckets;
        final IOrderBucket bucket = priceMapBucket.computeIfAbsent(cmd.price, p -> {
            final IOrderBucket b= IOrderBucket.create(IOrderBucket.OrderBucketImplType.GUDY);
            b.setPrice(p);
            return b;
        });
        bucket.put(order);
        oidMap.put(cmd.oid,order);
        return CmdResultCode.SUCCESS;
    }

    private long preMatch(RbCmd cmd, SortedMap<Long, IOrderBucket> matchBuckets) {
        int tVol = 0;
        if(matchBuckets.size()==0){
            return tVol;
        }
        List<Long> emptyBuckets = Lists.newArrayList();
        for (IOrderBucket bucket : matchBuckets.values()) {
            bucket.match(cmd.volume-tVol,cmd,order->{
                oidMap.remove(order.getOid());
            });
            if(bucket.getTotalVolume() == 0){
                emptyBuckets.add(bucket.getPrice());
            }
            if(tVol == cmd.volume){
                break;
            }
        }
        emptyBuckets.forEach(matchBuckets::remove);
        return tVol;
    }

    /**
     *
     * @param cmd
     * @param status
     */
    private void genMatchEvent(RbCmd cmd, OrderStatus status) {
        long now = System.currentTimeMillis();
        MatchEvent event = new MatchEvent();
        event.timestamp=now;
        event.mid=cmd.mid;
        event.oid=cmd.oid;
        event.status=status;
        event.volume=0;
        cmd.matchEventList.add(event);
    }

    @Override
    public CmdResultCode cancelOrder(RbCmd cmd) {
        //1 从缓存中移除委托
        Order order = oidMap.get(cmd.oid);
        if(order == null){
            return CmdResultCode.INVALID_ORDER_ID;
        }
        oidMap.remove(order.getOid());
        //2 从orderbucket中移除委托
        final NavigableMap<Long,IOrderBucket> buckets =
                order.getDirection() == OrderDirection.SELL?sellBuckets:buyBuckets;
        IOrderBucket orderBucket = buckets.get(order.getPrice());
        orderBucket.remove(order.getInnerOid());
        if(orderBucket.getTotalVolume()==0){
            buckets.remove(order.getPrice());
        }

        //发送撤单到MatchEvent
        MatchEvent ofrEvent = new MatchEvent();
        ofrEvent.timestamp = System.currentTimeMillis();
        ofrEvent.mid = order.getMid();
        ofrEvent.oid = order.getOid();
        ofrEvent.status = order.getTvolume() == 0 ? OrderStatus.CANCEL_ED : OrderStatus.PART_CANCEL;
        ofrEvent.volume = order.getTvolume()-order.getVolume();
        cmd.matchEventList.add(ofrEvent);

        return  CmdResultCode.SUCCESS;
    }

    @Override
    public void fillCode(L1MarketData marketData) {
        marketData.code = code;
    }

    @Override
    public void fillSells(int sellSize, L1MarketData marketData) {
        if(sellSize == 0){
            marketData.sellSize=0;
            return;
        }
        int i=0;
        for (IOrderBucket bucket : sellBuckets.values()) {
            marketData.sellPrices[i] = bucket.getPrice();
            marketData.sellVolumes[i] = bucket.getTotalVolume();
            if(++i == sellSize){
                break;
            }
            marketData.sellSize = i;
        }
    }

    @Override
    public void fillBuys(int buySize, L1MarketData marketData) {
        if(buySize == 0){
            marketData.buySize=0;
            return;
        }
        int i=0;
        for (IOrderBucket bucket : buyBuckets.values()) {
            marketData.buyPrices[i] = bucket.getPrice();
            marketData.buyVolumes[i] = bucket.getTotalVolume();
            if(++i == buySize){
                break;
            }
            marketData.buySize = i;
        }
    }

    @Override
    public int limitSellBucketSize(int maxSize) {
        int  limitSellBucketSize= Math.min(maxSize, sellBuckets.size());
        return limitSellBucketSize;
    }

    @Override
    public int limitBuyBucketSize(int maxSize) {
        int  limitBuyBucketSize= Math.min(maxSize, buyBuckets.size());
        return limitBuyBucketSize;
    }
}
